package com.tianji.learning.service.impl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.ObjectUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.mq.message.PointsMessage;
import com.tianji.learning.service.ISignRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;

/**
 * <p>
 * 学习积分记录，每个月底清零 服务实现类
 * </p>
 *
 * @author fx
 * @since 2024-08-29
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SignRecordServiceImpl implements ISignRecordService {
    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper mqHelper;

    /**
     * 01 每日签到（day07-随堂-01）
     * 每日签到结果
     * @return
     */
    @Override
    public SignResultVO addSignRecord() {
        // 1 签到
        // 1.1 获取key值--设置位图的key
        String key = getSignKey();
        // 1.2 计算offset值
        int offset = LocalDate.now().getDayOfMonth() - 1;
        // 1.3 签到 并校验是否 【重复签到】
        // 先判断是否存在签到记录，如果不存在，则为当月第一次签到，设置过期时间
        boolean keyExist = false;
        Long existList = redisTemplate.countExistingKeys(Collections.singleton(key));
        if (existList > 0) {
            keyExist = true;
        }

        // 设置位图，如果返回为【1-true】，说明已经签到过，不能重复签到
        Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);
        if (BooleanUtils.isTrue(exists)) {
            throw new BizIllegalException("不允许重复签到！！！");
        }
        // 1.4 若为【当月第一次签到】，设置【过期时间】
        if (BooleanUtils.isTrue(keyExist)) {
            LocalDateTime beginTime = LocalDateTime.now();
            LocalDateTime endTime = DateUtils.getMonthBeginTime(LocalDate.now().plusMonths(1));
            redisTemplate.expire(key, DateUtils.between(beginTime, endTime));
        }

        // 2 计算连续签到天数
        Integer signDays = countSignDays(key, LocalDate.now().getDayOfMonth());

        // 3 计算签到奖励积分
        Integer rewardPoints = computerRewardPoints(signDays);

        // 4 封装签到结果vo
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(signDays);
        vo.setRewardPoints(rewardPoints);

        // 4 TODO mq发送消息->积分系统，保存积分记录
        try {
            mqHelper.send(
                    MqConstants.Exchange.LEARNING_EXCHANGE,
                    MqConstants.Key.SIGN_IN,
                    PointsMessage.of(UserContext.getUser(),vo.totalPoints())
            );
        } catch (Exception e) {
            log.error("保存签到积分失败，错误原因：{}",e);
        }

        // 5 返回签到结果
        return vo;
    }

    /**
     * 02 查询当月签到记录（day07-随堂-02）
     *
     * @return
     */
    @Override
    public Byte[]/*List<Byte>*/ getSignRecords() {

        // 1.1 获取key值--设置位图的key
        String key = getSignKey();
        // 1.2 获取当前日期
        int dayOfMonth = LocalDate.now().getDayOfMonth();
        // 2、通过bitfield命令获取 本月从第一天到今天为止的所有签到记录
        List<Long> result = redisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
        );
        if (ObjectUtils.isEmpty(result)) {
            return /*Collections.emptyList()*/ null;
        }
        int signResult = result.get(0).intValue();

        // 3 定义一个数组，用于存储连续签到记录
       /* List<Byte> list=new ArrayList<>();
        int count = 0;
        // 3 用获取的签到记录，和1进行按位【与 &】运算，得到签到记录
        for (int i = 0; i < dayOfMonth; i++) {
            // 3.1 记录最新的一天是否签到
            list.add(0, (byte) (signResult & 1));
            // 3.2 签到记录右移一位，继续与1进行运算
            signResult >>>= 1;
        }*/
        Byte[] list =new Byte[dayOfMonth];
        for (int day = dayOfMonth-1; day >= 0; day--) {
            list[day]= (byte) (signResult & 1);
            signResult >>>= 1;
        }
        return list;
    }

    /**
     * tool-03 计算连续签到奖励积分
     *
     * @param signDays
     * @return
     */
    private Integer computerRewardPoints(Integer signDays) {
        switch (signDays) {
            case 7:
                return 10;
            case 14:
                return 20;
            case 28:
                return 40;
            default:
                return 0;
        }
    }

    /**
     * tool-02 计算当前用户连续签到天数
     *
     * @param key
     * @param dayOfMonth
     * @return
     */
    private Integer countSignDays(String key, int dayOfMonth) {
        // 1、通过bitfield命令获取 本月从第一天到今天为止的所有签到记录
        List<Long> result = redisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
        );
        if (ObjectUtils.isEmpty(result)) {
            return 0;
        }
        int signResult = result.get(0).intValue();
        // 2 定义一个计数器，计算连续签到天数
        int count = 0;
        // 3 用获取的签到记录，和1进行按位【与 &】运算，得到连续签到记录
        while ((signResult & 1) == 1) {
            // 3.1 与运算结果为1，说明连续签到，连续签到天数+1
            count++;
            // 3.2 签到记录右移一位，继续与1进行运算
            signResult >>>= 1;
        }
        // 4 返回连续签到天数
        return count;
    }

    /**
     * tool-01 设置并返回 key值--用户当月的key 用户签到的位图
     *
     * @return
     */
    private String getSignKey() {
        // 获取userId和日期，为设置key值做准备
        // 1 获取当前用户id
        Long userId = UserContext.getUser();
        // 2 获取当前日期
        LocalDate now = LocalDate.now();
        String dateStr = now.format(DateUtils.DATE_SUFFIX_FORMATTER);
        // 3 拼接key值
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId + dateStr;
        // 4 返回key
        return key;
    }


}
